<?php
// +-------------------------------------------------------------------
// | 
// +-------------------------------------------------------------------
// | Copyright (c) 2009-2016 All rights reserved.
// +-------------------------------------------------------------------
namespace Kcdns\Service\Sms;

/**
 * 短信等消息服务中心
 *
 * @package Service\Sms
 *         
 */
class Sms extends \Think\Model
{

    static $instance;

    protected $configPrefix = "sms_";

    protected $name = 'sms';

    protected $driverInstance = null;
    // 驱动实例
    protected $verifycodeSavePrefix = "VEFIFY_CODE_SESSION";

    protected $verifycodeMaxTimes = 5;
    // 单个验证码最多允许使用次数
    
    // protected $codeSet='23456789abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXYZ';//验证码字符集
    protected $codeSet = '0123456789';
    // 验证码字符集
    protected $codeLength = 4;
    // 验证码长度
    protected $codeExpire = 300;
    // 验证码有效期 默认300S
    protected $codeInterval = 30;
    // 验证码发送频率限制 默认30S
    protected $tplVariable = array();
    // 短信模板变量
    protected $sysErrorCode = "99999";
    // 短信系统错误 如每日发送总量超限
    protected $defaultSendUser = "web";
    // 短信默认发送用户 system:系统级用户(发送频率 及次数不受限制) web:站点用户（发送短信频率及次数受限)
    protected $sendUser = 'web';
    // 当前发送短信用户
    
    // 短信配置
    protected $config = array(
            'driver' => 'common', // 短信驱动
            'variable' => array(), // 短信模板变量
            'tpl' => array(), // 短信模板
            'limit' => 500, // 当日短信发送最大数量
            'ulimit' => 50, // 当日用户短信发送最大数量
            'hide_code'=>true, // 数据库中是否隐藏验证码
            'debug' => false
    ); // 是否开启调试模式

    // 短信驱动配置
    protected $driverConfig = array();

    

    public function _initialize ()
    {
        
        // 加载配置
        $this->config = $this->_loadConfig();
        $this->driverConfig = $this->_loadDriverConfig();
        $this->driverInstance = $this->_loadDriver();
        $this->tplVariable = $this->config['variable'];
    }

    public static function getInstance ()
    {
        self::$instance or self::$instance = new self();
        return self::$instance;
    }

    /*
     * 发送短信
     */
    public function sendMsg ($mobile, $content, $tpl = "", $sendUser = "web")
    {
        // 传入模板时, $content 作为替换变量数组
        if ($tpl)
        {
            $this->tplVariable = $content;
            $content = '';
        }
        $this->sendUser = $sendUser ? $sendUser : $this->defaultSendUser;
        return $this->_send($mobile, $content, $tpl, $this->sendUser);
    }

    /*
     * 短信重发
     */
    public function resend ($smsId, $mobile, $content, $sendUser)
    {
        $where = array(
                'id' => $smsId
        );
        $record = $this->where($where)->find();
        if (! $record)
        {
            return false;
        }
        $target = $mobile ? $mobile : $record['target'];
        $content = $content ? $content : $record['content'];
        $sendUser = $sendUser ? $sendUser : $this->defaultSendUser;
        return $this->_send($target, $content, $tpl = "", $sendUser);
    }

    /**
     * 发送短信验证码
     *
     * @param string $sceneId
     *            短信验证码场景值
     */
    public function sendCode ($sceneId, $mobile, $tpl)
    {
        $mobilePatt = "/1[0-9]{10}/";
        if (! preg_match($mobilePatt, $mobile))
        {
            throw new \Exception('invalid param');
        }
        $verifyCode = $this->_createVerifyCode($mobile, $sceneId);
        $this->_setTplVariable('VERIFYCODE', $verifyCode);
        return $this->_send($mobile, $verifyCode, $tpl, $this->defaultSendUser);
//        return $verifyCode;
    }

    /**
     * 验证短信验证码
     *
     * @param string $mobile
     *            短信验证的手机号
     * @param string $sceneId
     *            短信验证码场景值
     * @param string $verifyCode
     *            收到的短信验证码
     * @param bool $isdelete
     *            验证码校验后是否删除（ajax异步校验 不需要立即删除)
     */
    public function checkCode ($mobile, $sceneId, $verifyCode, $isdelete)
    {
        if (! $verifyCode)
        {
            return false;
        }
        $status = $this->_getVerifyCode($mobile, $sceneId) === $verifyCode;
        if ($status && $isdelete)
        {
            $this->_clearVerifyCode($mobile, $sceneId);
        }
        if (! $status)
        {
            // 累计错误使用次数
            $this->_increaseVerifyCodeCount($mobile, $sceneId);
        }
        return $status;
    }

    public function getMessage ($tpl, $data)
    {
        $this->tplVariable = $data;
        return $this->_parseTpl($tpl);
    }

    /**
     * 生成短信验证码
     */
    private function _createVerifyCode ($mobile, $sceneId)
    {
        $verifyCode = "";
        for ($i = 0; $i < $this->codeLength; $i ++)
        {
            $verifyCode .= $this->codeSet[mt_rand(0, strlen($this->codeSet) - 1)];
        }
        $this->_saveVerifyCode($mobile, $sceneId, $verifyCode);
        return $verifyCode;
    }

    /**
     * （使用)获取验证码
     */
    private function _getVerifyCode ($mobile, $sceneId)
    {
        $verifyCode = session($this->_getVerifyKey($mobile, $sceneId));
        // 验证码有效 且未超过错误重试次数 且尚在有效期内
        if ($verifyCode && $verifyCode['count'] < $this->verifycodeMaxTimes && time() - $verifyCode['time'] < $this->codeExpire)
        {
            return $verifyCode['value'];
        }
        else
        {
            return false;
        }
    }

    /**
     * 获取短信验证码存储KEY
     */
    public function _getVerifyKey ($mobile, $sceneId)
    {
        return $this->verifycodeSavePrefix . '_' . $sceneId . '_' . $mobile;
    }

    /**
     * 存储验证码
     */
    private function _saveVerifyCode ($mobile, $sceneId, $verifyCode, $count = 0)
    {
        $data = array(
                'value' => $verifyCode,
                'count' => $count,
                'time' => time()
        );
        session($this->_getVerifyKey($mobile, $sceneId), $data);
        return true;
    }

    /**
     * 验证码使用计数
     */
    private function _increaseVerifyCodeCount ($mobile, $sceneId)
    {
        $verifyCode = session($this->_getVerifyKey($mobile, $sceneId));
        $verifyCode['count'] ++;
        return session($this->_getVerifyKey($mobile, $sceneId), $verifyCode);
    }

    /**
     * 清除验证码
     */
    private function _clearVerifyCode ($mobile, $sceneId)
    {
        return session($this->_getVerifyKey($mobile, $sceneId), null);
    }

    /**
     * 解析短信模板
     */
    private function _parseTpl ($tpl)
    {
        $tplList = $this->config['tpl'];
        $patt = "/(\{(\w+)\})/";
        $tplVariable = $this->tplVariable;
        $content = preg_replace_callback($patt, function  ($match) use( $tplVariable) {
            return $tplVariable[$match[2]];
        }, $tplList[$tpl]);
        return $content;
    }

    /**
     * 设置短信模板变量
     */
    private function _setTplVariable ($key, $value)
    {
        $this->tplVariable[$key] = $value;
    }

    /**
     * 短信发送执行
     */
    private function _send ($mobile, $content, $tpl, $sendUser)
    {
        // 系统变量
        $this->_setTplVariable('MOBILE', $mobile);
        // \KCSLog::WARN('dddddddddddddddddddd911'.print_r($tpl, true));
        // \KCSLog::WARN('dddddddddddddddddddd911'.print_r($this->config['tpl'], true));
        // 短信内容解析
        $content = $tpl ? $this->_parseTpl($tpl) : $content;
        // 短信发送最大次数限制验证
        if ($this->_safeCheck($mobile) !== true)
        {
            // 异常记录
            $this->_record($mobile, $this->_hideContent($content), $status = false, $response = "", $error = $this->getError(), $this->sysErrorCode);
            throw new \Exception($this->getError());
        }
        if ($this->config['debug'])
        {
            $response = $this->driverInstance->parseResponse(true, 'debug', 0, '');
        }
        else
        {
            $response = $this->driverInstance->send($mobile, $content);
        }
        
        
        $this->_record($mobile, $this->_hideContent($content), $response['status'], $response['response'], $response['error'], $response['errorCode'], $sendUser);
        // 缓存该用户上一次发送时间
        S('SMS_LAST_TIME_' . $mobile, time());
        return $response['status'] === true;
    }

    /**
     * 短信安全校验
     * 当天短信总量安全校验
     */
    private function _safeCheck ($mobile)
    {
        // 系统级用户 发送短信 不受限制(慎用)
        if ($this->sendUser == 'system')
        {
            return true;
        }
        $limitOfDay = $this->config['limit'];
        $limitOfUser = $this->config['ulimit'];
        $sendIntval = (int) $this->config['intval'] ?  : $this->codeInterval;
        
        // 发送频率验证
        $lastSendTime = S('SMS_LAST_TIME_' . $mobile);
        if ($lastSendTime && time() - $lastSendTime <= $sendIntval)
        {
            $this->error = "短信发送频率过快(还剩" . ($sendIntval - (time() - $lastSendTime)) . "秒)";
            return false;
        }
        // 单日总量验证
        $where = array(
                'create_time' => array(
                        'egt',
                        date('Y-m-d')
                )
        );
        $totalCount = $this->where($where)->count();
        if ($totalCount >= $limitOfDay)
        {
            $this->error = "当日短信发送超限";
            return false;
        }
        // 用户单日总量验证
        $where['target'] = $mobile;
        $userCount = $this->where($where)->count();
        if ($userCount >= $limitOfUser)
        {
            $this->error = "用户当日短信发送超限";
            return false;
        }
        return true;
    }

    /**
     * 短信发送记录
     */
    private function _record ($mobile, $content, $status, $response, $error, $errorcode, $sendUser)
    {
        $record = array(
                'driver' => $this->config['driver'],
                'target' => $mobile,
                'content' => $content,
                'create_time' => date('Y-m-d H:i:s'),
                'status' => $status,
                'response' => $response,
                'error' => $error,
                'errcode' => $errorcode,
                'send_user' => $sendUser ?  : $this->defaultSendUser
        );
        return $this->add($record);
    }

    /**
     * 加载短信驱动
     */
    private function _loadDriver ()
    {
        $driver = $this->config['driver'];
        // 读取驱动配置
        $driverClass = "\\Kcdns\\Service\\Sms\\drivers\\" . ucfirst(strtolower($driver));
        $driverConfig = $this->driverConfig;
        if (class_exists($driverClass))
        {
            return new $driverClass($driverConfig['api'], $driverConfig['params']);
        }
        else
        {
            throw new \Exception('(' . $driverClass . ') sms driver is not exists');
        }
    }

    /**
     * 加载短信配置配置
     */
    private function _loadConfig ()
    {
        $defaultConfig = $this->config;
        $config = array();
        
        $driver = C('sms_driver');
        $variable = $this->_parseConfig(C('sms_variable'));
        $tpl = $this->_parseConfig(C('sms_tpl'));
        $limit = C('sms_limit');
        $ulimit = C('sms_ulimit');
        $debug = C("sms_debug");
        $intval = C("sms_intval");
        $hideCode = C("sms_hide_code");
        
        $driver && $config['driver'] = $driver; // 驱动
        $variable && $config['variable'] = $variable; // 变量
        $tpl && $config['tpl'] = $tpl; // 模板
        $limit && $config['limit'] = $limit; // 单日限额
        $ulimit && $config['ulimit'] = $ulimit; // 单日用户限额
        $debug && $config['debug'] = $debug;
        $intval && $config['intval'] = $intval; // 用户发送频率限制（该秒以内 不得重复发送)
        isset($hideCode) and $config['hide_code'] = $hideCode; // 发送日志是否隐藏验证码
        
        return array_merge($defaultConfig, $config);
    }

    /**
     * 加载驱动配置
     */
    public function _loadDriverConfig ()
    {
        $api = C('sms_api');
        $params = $this->_parseConfig(C('sms_params'));
        
        return array(
                'api' => $api,
                'params' => $params
        );
    }

    public function _parseConfig ($config)
    {
//        $arr = explode("\n", $config);
//        $response = array();
//        foreach ($arr as $item)
//        {
//            $itemInfo = explode('=>', $item);
//            $itemInfo[0] = trim($itemInfo[0]);
//            $itemInfo[1] = trim($itemInfo[1]);
//            if ($itemInfo[0])
//            {
//                $response[$itemInfo[0]] = $itemInfo[1];
//            }
//        }
        parse_str($config,$response);
        return $response;
    }

    // 入库时隐藏验证码
    protected function _hideContent($content){
        if($this->config['hide_code'] && $this->tplVariable['VERIFYCODE']){
            //$content = str_replace($this->tplVariable['VERIFYCODE'], str_repeat('*', strlen($this->tplVariable['VERIFYCODE'])), $content);
        }
        return $content;
    }
}